/*
	The Raja Toolkit.
	Copyright (C) 2011 Raja-Toolkit Foundation

	This library is free software; you can redistribute it and/or
	modify it under the terms of the GNU Lesser General Public
	License as published by the Free Software Foundation; either
	version 3.0 of the License, or (at your option) any later version.

	This library is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
	Lesser General Public License for more details.

	You should have received a copy of the GNU Lesser General Public
	License along with this library; if not, write to the Free Software
	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/

// in this file we use external templates

#ifndef PRIMITIVES_HPP
#define PRIMITIVES_HPP

#include "../types.hpp"
#include "vector.hpp"
#include "matrix.hpp"
#include "quaternion.hpp"
#include "transforms.hpp"

namespace raja
{
namespace math
{

///////////////////////////////////////////////////////////////////////////////

///
/// 2D bounding box primitive
///
template<typename T>
struct tbox
{
	T						center;
	T						extent;

	inline					tbox() : center(0), extent(0)	{}

	inline					tbox(const T& center_, const T extent_) : center(center_), extent(extent_)	{}

	inline T				get_min() const { return (center - extent); }
	inline T				get_max() const { return (center + extent); }

	inline tbox&			operator += (const T& pt)
	{
		T		mn	= center - extent;
		T		mx	= center + extent;

		mn		= min(mn, pt);
		mx		= max(mx, pt);

		center	= half(mn + mx);
		extent	= mx - center;
		return *this;
	}

	tbox&					operator +=(const tbox& b)
	{
		T		mn	= b.get_min();
		T		mx	= b.get_max();

		mn		= min(mn, get_min());
		mx		= max(mx, get_max());

		center	= half(mn + mx);
		extent	= mx - center;
		return *this;
	}

};

typedef tbox<ivec2>		ibox2;
typedef tbox<vec2>		box2;
typedef tbox<dvec2>		dbox2;

typedef tbox<ivec3>		ibox3;
typedef tbox<vec3>		box3;
typedef tbox<dvec3>		dbox3;

///////////////////////////////////////////////////////////////////////////////

///
/// line segment
///
template<typename T>
struct tsegment
{
	typedef typename T::atom_type atom_type;

	T						start;
	T						end;

	inline					tsegment() : start(0), end(0)	{}
	inline					tsegment(const T& start_, const T& end_) : start(start_), end(end_)	{}
	inline					tsegment(const tsegment& s) : start(s.start), end(s.end)	{}

	///
	/// @brief get the length
	/// @return segment length
	///
	inline atom_type		length() const
	{
		return raja::math::length(end - start);
	}

	///
	/// @brief get point at parametric distance
	///
	/// In case the parametric distance \f$t\f$ is such that \f$0 \leq t \leq 1\f$ then the returned
	/// point is inside the line segment.
	/// @return the point at parametric distance
	///
	inline T				operator[] (atom_type t) const
	{
		return ((end - start) * t + start);
	}
};

typedef tsegment<vec2>		segment2;
typedef tsegment<dvec2>		dsegment2;

typedef tsegment<vec3>		segment3;
typedef tsegment<dvec3>		dsegment3;

///////////////////////////////////////////////////////////////////////////////

///
/// ray
///
template<typename T>
struct tray
{
	typedef typename T::atom_type	atom_type;
	T						start;
	T						direction;

	inline					tray() : start(0), direction(0)	{}
	inline					tray(const T& start_, const T& direction_) : start(start_), direction(direction_)	{}
	inline					tray(const tray& r) : start(r.start), direction(r.direction)	{}

	///
	/// @brief get point at parametric distance
	/// @return the point at parametric distance
	///
	inline T				operator[] (atom_type t) const
	{
		return (direction * t + start);
	}

	///
	/// @brief normalize the ray direction
	///
	inline tray&				normalize()
	{
		direction	= raja::math::normalize(direction);
		return *this;
	}
};

typedef tray<vec2>			ray2;
typedef tray<dvec2>			dray2;

typedef tray<vec3>			ray3;
typedef tray<dvec3>			dray3;

///////////////////////////////////////////////////////////////////////////////

///
/// straight line
///
template<typename T>
struct tline
{
	typedef typename T::atom_type	atom_type;
	T						point;
	T						direction;

	inline					tline()	: point(0), direction(0)				{}
	inline					tline(const T& point_, const T& direction_) : point(point_), direction(direction_)	{}
	inline					tline(const tline& l) : point(l.point), direction(l.direction)		{}

	///
	/// @brief get point at parametric distance from point
	/// @return the point at parametric distance
	///
	inline T				operator[] (atom_type t) const
	{
		return (direction * t + point);
	}

	///
	/// @brief normalize the ray direction
	///
	inline tline&			normalize()
	{
		direction	= raja::math::normalize(direction);
		return *this;
	}
};

typedef tline<vec2>			line2;
typedef tline<dvec2>		dline2;

typedef tline<vec3>			line3;
typedef tline<dvec3>		dline3;


///////////////////////////////////////////////////////////////////////////////

///
/// plane
///
template<typename T>
struct tplane
{
	T						a, b, c, d;

	inline					tplane(T a_, T b_, T c_, T d_) : a(a_), b(b_), c(c_), d(d_)
	{
	}

	inline					tplane(const tvec3<T>& normal, T cst) : a(normal.x), b(normal.y), c(normal.z), d(cst)
	{
	}

	inline					tplane(const tplane<T>& p) : a(p.a), b(p.b), c(p.c), d(p.d)
	{
	}

	///
	/// @brief get plane normal
	/// @return plane normal
	///
	inline tvec3<T>			normal() const
	{
		return tvec3<T>(a, b, c);
	}

	///
	/// @brief get the plane constant
	/// @return the plane constant
	///
	inline T				constant() const
	{
		return d;
	}
};

///
/// @brief normalize the plane
/// @param p the plane to normalize
/// @return normalized plane
///
template<typename T>
tplane<T>		normalize(const tplane<T>& p);

typedef tplane<real32>	plane;
typedef tplane<real64>	dplane;

///////////////////////////////////////////////////////////////////////////////

///
/// triangle2
///
template<typename T>
struct ttri2
{
	tvec2<T>				v[3];

	inline					ttri2()
	{
		v[0]	= v[1]	= v[2]	= tvec2<T>();
	}

	inline					ttri2(const tvec2<T>& v0, const tvec2<T>& v1, const tvec2<T>& v2)
	{
		v[0]	= v0;
		v[1]	= v1;
		v[2]	= v2;
	}

	inline					ttri2(const tvec2<T> v_[3])
	{
		v[0]	= v_[0];
		v[1]	= v_[1];
		v[2]	= v_[2];
	}

	inline					ttri2(const ttri2<T>& t)
	{
		v[0]	= t.v[0];
		v[1]	= t.v[1];
		v[2]	= t.v[2];
	}

	///
	/// @brief convert from barycentric coordinates to system coordinates
	/// @param p input barycentric coordinates
	/// @return corresponding system coordinates
	///
	tvec2<T>				convert_barycentric_coord(const tvec2<T>& p) const;

	///
	/// @brief get the area of the triangle
	/// @return area of the triangle
	///
	T						area() const;

	///
	/// @brief get the triangle vertex
	/// @param i triangle vertex index (0 to 2)
	/// @return the triangle vertex
	///
	inline const tvec2<T>	operator[] (uint32 i) const
	{
		return	v[i];
	}

	///
	/// @brief get the triangle vertex
	/// @param i triangle vertex index (0 to 2)
	/// @return the triangle vertex
	///
	inline tvec2<T>&		operator[] (uint32 i)
	{
		return	v[i];
	}


	///
	/// @brief get the barycentric coordinate
	/// @param p the point in the original coordinate system
	/// @return the barycentric coordinate (where x is along v[0]v[1], y along v[0]v[2])
	///
	tvec2<T>				operator[] (const tvec2<T>& p) const;

};

typedef ttri2<real32>	tri2;
typedef ttri2<real64>	dtri2;

///////////////////////////////////////////////////////////////////////////////

///
/// triangle3
///
template<typename T>
struct ttri3
{
	tvec3<T>		v[3];

	inline					ttri3()
	{
		v[0]	= v[1]	= v[2]	= tvec3<T>();
	}

	inline					ttri3(const tvec3<T>& v0, const tvec3<T>& v1, const tvec3<T>& v2)
	{
		v[0]	= v0;
		v[1]	= v1;
		v[2]	= v2;
	}

	inline					ttri3(const tvec3<T> v_[3])
	{
		v[0]	= v_[0];
		v[1]	= v_[1];
		v[2]	= v_[2];
	}

	inline					ttri3(const ttri3<T>& t)
	{
		v[0]	= t.v[0];
		v[1]	= t.v[1];
		v[2]	= t.v[2];
	}

	///
	/// @brief convert from barycentric coordinates to system coordinates
	/// @param p input barycentric coordinates
	/// @return corresponding system coordinates
	///
	tvec3<T>				convert_barycentric_coord(const tvec3<T>& p) const;

	///
	/// @brief get the area of the triangle
	/// @return area of the triangle
	///
	T						area() const;

	///
	/// @brief get the triangle vertex
	/// @param i triangle vertex index (0 to 2)
	/// @return the triangle vertex
	///
	inline const tvec3<T>	operator[] (uint32 i) const
	{
		return	v[i];
	}

	///
	/// @brief get the triangle vertex
	/// @param i triangle vertex index (0 to 2)
	/// @return the triangle vertex
	///
	inline tvec3<T>&		operator[] (uint32 i)
	{
		return	v[i];
	}


	///
	/// @brief get the barycentric coordinate
	/// @param p the point in the original coordinate system
	/// @return the barycentric coordinate (where x is along v[0]v[1], y along v[0]v[2])
	///
	tvec3<T>				operator[] (const tvec3<T>& p) const;

};

typedef ttri3<real32>	tri3;
typedef ttri3<real64>	dtri3;

}	// namespace math
}	// namespace raja

#endif // PRIMITIVES_HPP
